/*
 *该程序实现的是在内核态的一个定时任务，作为实验实例，是在2.6下的字符模块的基础上作的。
 *过程是在用户态程序使用这个字符驱动时，如果要读取数据，则不会立即读取，而是在过一段时间后再读取，
 *这个时间就是在内核中设定的定时。
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/sched.h>

#define DEVICE_NAME "hello_chardev"

static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);

static struct file_operations chardev_fops = {
    .read = chardev_read,
};

static struct cdev *cdevp = NULL;
static dev_t devno;
static wait_queue_head_t queue;
static int flag = 0;
static struct timer_list timer;

static void timer_callback(unsigned long arg)
{
    struct task_struct *p=(struct task_struct *)arg;

    printk("<4>callback...%ld %ld\n",timer.expires,jiffies);
    flag = 1;
    wake_up_interruptible(&queue);
    //wake_up_process(p);
}

static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    char sendbuf[] = "hello world";

    init_timer(&timer);
    timer.expires = jiffies + HZ * 10;
    timer.data =(unsigned long)current;

    timer.function = timer_callback;

    add_timer(&timer);

    printk("<4> HZ...%d\n",HZ);
    printk("<4> before schedule...%ld %ld\n",timer.expires,jiffies);

    wait_event_interruptible(queue, flag != 0);
    //schedule();
    printk("<4>time out...%ld %ld\n",timer.expires,jiffies);
    flag = 0;
    del_timer(&timer);
    copy_to_user(buf, sendbuf, strlen(sendbuf));
    
    return strlen(buf);
}

static int __init chardev_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
    if (ret < 0)
        goto out;
    cdevp = cdev_alloc();
    if (!cdevp)
        goto alloc_err;

    init_waitqueue_head(&queue);

    cdev_init(cdevp, &chardev_fops);

    ret = cdev_add(cdevp, devno, 1);
    if (!ret)
        goto out;
    cdev_del(cdevp);
alloc_err:
    unregister_chrdev_region(devno, 1);
out:
    return ret;
}

static void __exit chardev_exit(void)
{
    cdev_del(cdevp);
    unregister_chrdev_region(devno, 1);

    return;
}

MODULE_LICENSE("GPL");

module_init(chardev_init);
module_exit(chardev_exit);
